﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;

namespace NMVN.Common
{
    public partial class AutoCompleteComboBox : ComboBox, IMessageFilter
    {
        private Control ComboParentForm; // Or use type "Form" 
        private ListBox listBoxChild;
        private int IgnoreTextChange;
        private bool MsgFilterActive = false;
        private bool autoMatching = true;
        private string autoMatchingMode = "Match Case";  


        public AutoCompleteComboBox()
        {
            // Set up all the events we need to handle
            if (AutoMatching)
            {
                TextChanged += AutoCompleteComboBox_TextChanged;
                SelectionChangeCommitted += AutoCompleteComboBox_SelectionChangeCommitted;
                LostFocus += AutoCompleteComboBox_LostFocus;
                MouseDown += AutoCompleteComboBox_MouseDown;
                HandleDestroyed += AutoCompleteComboBox_HandleDestroyed;
            }

        }

        [Browsable(true)]
        public bool AutoMatching
        {
            get
            {
                return autoMatching;
            }
            set
            {
                autoMatching = value;
            }
        }

        [TypeConverter(typeof(AutoMatchingConverter)), CategoryAttribute("Document Settings")]
        public string AutoMatchingMode
        {
            get { return autoMatchingMode; }
            set { autoMatchingMode = value; }
        }

        void AutoCompleteComboBox_HandleDestroyed(object sender, EventArgs e)
        {
            if (MsgFilterActive)
                Application.RemoveMessageFilter(this);
        }

        ~AutoCompleteComboBox()
        {
        }

        private void AutoCompleteComboBox_MouseDown(object sender, MouseEventArgs e)
        {
            HideTheList();
        }

        void AutoCompleteComboBox_LostFocus(object sender, EventArgs e)
        {
            if (listBoxChild != null && !listBoxChild.Focused)
                HideTheList();
        }

        void AutoCompleteComboBox_SelectionChangeCommitted(object sender, EventArgs e)
        {
            IgnoreTextChange++;
        }

        void InitListControl()
        {
            if (listBoxChild == null)
            {
                //// Find parent - or keep going up until you find the parent form
                ComboParentForm = this.FindForm();

                if (ComboParentForm != null)
                {
                    // Setup a messaage filter so we can listen to the keyboard
                    if (!MsgFilterActive)
                    {
                        Application.AddMessageFilter(this);
                        MsgFilterActive = true;
                    }

                    listBoxChild = listBoxChild = new ListBox();
                    listBoxChild.Visible = false;
                    ComboParentForm.Controls.Add(listBoxChild);
                    ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
                }
            }
        }


        void AutoCompleteComboBox_TextChanged(object sender, EventArgs e)
        {
            if (!AutoMatching)
            {
                return;
            }

            if (IgnoreTextChange > 0)
            {
                IgnoreTextChange = 0;
                return;
            }

            InitListControl();

            if (listBoxChild == null || string.IsNullOrEmpty(this.DisplayMember))
            {
                return;
            }

            string SearchText = this.Text;

            listBoxChild.Items.Clear();

            // Don't show the list when nothing has been typed
            if (!string.IsNullOrEmpty(SearchText))
            {
                if (AutoMatchingMode == "Match Case")
                {
                    foreach (DataRowView dataRow in this.Items)
                    {
                        string Item = dataRow[this.DisplayMember].ToString();
                        if (Item != null && Item.Contains(SearchText))
                            listBoxChild.Items.Add(Item);
                    }
                }
                else
                {
                    foreach (DataRowView dataRow in this.Items)
                    {
                        string Item = dataRow[this.DisplayMember].ToString().ToLower();
                        string ItemAdd = dataRow[this.DisplayMember].ToString();
                        if (Item != null && Item.Contains(SearchText.ToLower()))
                            listBoxChild.Items.Add(ItemAdd);
                    }
                }
            }

            if (listBoxChild.Items.Count > 0)
            {
                if (SearchText.Length == listBoxChild.Items[0].ToString().Length && listBoxChild.Items.Count == 1)
                {
                    HideTheList();
                }
                else
                {
                    Point PutItHere = new Point(this.Left, this.Bottom);
                    Control TheControlToMove = this;

                    PutItHere = this.Parent.PointToScreen(PutItHere);

                    TheControlToMove = listBoxChild;
                    PutItHere = ComboParentForm.PointToClient(PutItHere);

                    TheControlToMove.Show();
                    TheControlToMove.Left = PutItHere.X;
                    TheControlToMove.Top = PutItHere.Y;
                    TheControlToMove.Width = this.Width;

                    int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
                    TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
                }
            }
            else
                HideTheList();
        }

        /// <summary>
        /// Copy the selection from the list-box into the combo box
        /// </summary>
        private void CopySelection()
        {
            if (listBoxChild.SelectedItem != null)
            {

                this.SelectedIndex = this.FindString(listBoxChild.SelectedItem.ToString());
                HideTheList();
                this.SelectAll();
            }
        }

        private void listBox1_Click(object sender, EventArgs e)
        {
            var ThisList = sender as ListBox;

            if (ThisList != null)
            {
                // Copy selection to the combo box
                CopySelection();
            }
        }

        public void HideTheList()
        {
            if (listBoxChild != null)
                listBoxChild.Hide();
        }

        public bool PreFilterMessage(ref Message m)
        {
            if (m.Msg == 0x201) // Mouse click: WM_LBUTTONDOWN
            {
                var Pos = new Point((int)(m.LParam.ToInt32() & 0xFFFF), (int)(m.LParam.ToInt32() >> 16));

                var Ctrl = Control.FromHandle(m.HWnd);
                if (Ctrl != null)
                {
                    // Convert the point into our parent control's coordinates ...
                    Pos = ComboParentForm.PointToClient(Ctrl.PointToScreen(Pos));

                    // ... because we need to hide the list if user clicks on something other than the list-box
                    if (ComboParentForm != null)
                    {
                        if (listBoxChild != null &&
                            (Pos.X < listBoxChild.Left || Pos.X > listBoxChild.Right || Pos.Y < listBoxChild.Top || Pos.Y > listBoxChild.Bottom))
                        {
                            if (listBoxChild.Items.Count > 0)
                            {
                                listBoxChild.SelectedIndex = 0;
                                CopySelection();
                            }
                            this.HideTheList();
                        }
                        else
                        {
                            if (listBoxChild != null)
                            {
                                Point point = listBoxChild.PointToClient(Cursor.Position);
                                int index = listBoxChild.IndexFromPoint(point);
                                if (index >= 0)
                                {
                                    listBoxChild.SelectedIndex = index;
                                    CopySelection();
                                }
                            }
                        }
                    }
                }
            }
            else if (m.Msg == 0x100) // WM_KEYDOWN
            {

                if (listBoxChild != null && listBoxChild.Visible)
                {
                    switch (m.WParam.ToInt32())
                    {
                        case 0x1B: // Escape key
                            this.HideTheList();
                            return true;

                        case 0x26: // up key                        
                        case 0x28: // down key
                            // Change selection
                            int NewIx = listBoxChild.SelectedIndex + ((m.WParam.ToInt32() == 0x26) ? -1 : 1);

                            // Keep the index valid!
                            if (NewIx > listBoxChild.Items.Count - 1 || NewIx < 0)
                            {
                                listBoxChild.SelectedIndex = listBoxChild.Items.Count - 1;
                            }
                            else
                            {
                                listBoxChild.SelectedIndex = NewIx;
                            }
                            return true;

                        case 0x21: //Page Up
                            listBoxChild.SelectedIndex = 0;
                            return true;

                        case 0x22: //Page Down
                            listBoxChild.SelectedIndex = listBoxChild.Items.Count - 1;
                            return true;

                        case 0x0D: // return (use the currently selected item)
                            CopySelection();
                            return true;
                    }
                }
            }

            return false;
        }
    }


    class AutoMatchingConverter : StringConverter
    {
        public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
        {
            return true;
        }

        public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
        {
            return new StandardValuesCollection(new string[] { "Match Case", "Ignore Case" });
        }

        public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
        {
            return false;
        }
    }
}
